﻿using Albatross.SharePoint.Configuration;
using Albatross.WPF.Common;
using Albatross.WPF.Common.ViewModels;
using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace Albatross.SharePoint.Explorer.ViewModels {
	public class ClientObjectViewModel : WorkspaceViewModel, IComparable<ClientObjectViewModel> {
		public const string SchemaXml_Property = "SchemaXml";

		public ObservableCollection<ClientObjectViewModel> Children { get; private set; }
		public ObservableCollection<ObjectProperty> Properties { get; private set; }
		public ObservableCollection<ObjectProperty> Selected { get; private set; }

		public ClientObjectViewModel(ClientContext context, string name,  ClientObject clientObject) {
			Name = name;
			Context = context;
			ClientObject = clientObject;
			Children = new ObservableCollection<ClientObjectViewModel>();
			Properties = new ObservableCollection<ObjectProperty>();
			Selected = new ObservableCollection<ObjectProperty>();
			TreeItemTitle = name;
			Title = name;
		}

		#region properties
		public ClientContext Context { get; private set; }
		public ClientObject ClientObject { get; private set; }
		public bool DataLoaded { get; private set; }
		public bool ReloadObject { get; set; }
		public string Name { get; private set; }
		object _sync = new object();

		public const string SchemaXmlPropertyName = "SchemaXml";
		private XmlDocument _schemaXml;
		public XmlDocument SchemaXml {
			get { return _schemaXml; }
			set {
				if (_schemaXml == value) { return; }
				RaisePropertyChanging(SchemaXmlPropertyName);
				_schemaXml = value;
				RaisePropertyChanged(SchemaXmlPropertyName);
			}
		}

		public virtual bool IsHidden { get { return false; } }

		public const string ViewServicePropertyName = "ViewService";
		private IViewService _viewSvcs;
		public IViewService ViewService {
			get { return _viewSvcs; }
			set {
				if (_viewSvcs == value) { return; }
				RaisePropertyChanging(ViewServicePropertyName);
				_viewSvcs = value;
				RaisePropertyChanged(ViewServicePropertyName);
			}
		}

		public const string TreeItemTitlePropertyName = "TreeItemTitle";
		private string _treeItemTitle;
		public string TreeItemTitle {
			get { return _treeItemTitle; }
			set {
				if (_treeItemTitle == value) { return; }
				RaisePropertyChanging(TreeItemTitlePropertyName);
				_treeItemTitle = value;
				RaisePropertyChanged(TreeItemTitlePropertyName);
			}
		}
		
		public const string TypeNamePropertyName = "TypeName";
		private string _typeName;
		public string TypeName {
			get { return _typeName; }
			set {
				if (_typeName == value) { return; }
				RaisePropertyChanging(TypeNamePropertyName);
				_typeName = value;
				RaisePropertyChanged(TypeNamePropertyName);
			}
		}

		public const string ShowHiddenObjectsPropertyName = "ShowHiddenObjects";
		private bool _showHiddenObjects;
		public bool ShowHiddenObjects {
			get { return _showHiddenObjects; }
			set {
				if (_showHiddenObjects == value) { return; }
				RaisePropertyChanging(ShowHiddenObjectsPropertyName);
				_showHiddenObjects = value;
				RaisePropertyChanged(ShowHiddenObjectsPropertyName);
			}
		}

		public const string HasSchemaXmlPropertyName = "HasSchemaXml";
		private bool _hasSchemaXml;
		public bool HasSchemaXml {
			get { return _hasSchemaXml; }
			set {
				if (_hasSchemaXml == value) { return; }
				RaisePropertyChanging(HasSchemaXmlPropertyName);
				_hasSchemaXml = value;
				RaisePropertyChanged(HasSchemaXmlPropertyName);
			}
		}
		public string SchemaXmlText { get; private set; }

		public const string SchemaXmlTabSelectedPropertyName = "SchemaXmlTabSelected";
		private bool _schemaXmlTabSelected;
		public bool SchemaXmlTabSelected {
			get { return _schemaXmlTabSelected; }
			set {
				if (_schemaXmlTabSelected == value) { return; }
				RaisePropertyChanging(SchemaXmlTabSelectedPropertyName);
				_schemaXmlTabSelected = value;
				RaisePropertyChanged(SchemaXmlTabSelectedPropertyName);
				if (value && HasSchemaXml && SchemaXml == null) {
				}
			}
		}
		
		public const string IsSelectedPropertyName = "IsSelected";
		private bool _isSelected;
		public bool IsSelected {
			get { return _isSelected; }
			set {
				if (_isSelected == value) { return; }
				RaisePropertyChanging(IsSelectedPropertyName);
				_isSelected = value;
				RaisePropertyChanged(IsSelectedPropertyName);
				if (value && ViewService != null){
					LoadAsync(ViewService);
					ViewService.ShowWorkspace(this);
				}
			}
		}
		
		public const string IsExpandedPropertyName = "IsExpanded";
		private bool _isExpanded;
		public bool IsExpanded {
			get { return _isExpanded; }
			set {
				if (_isExpanded == value) { return; }
				RaisePropertyChanging(IsExpandedPropertyName);
				_isExpanded = value;
				RaisePropertyChanged(IsExpandedPropertyName);
				if (value && ViewService != null) {
					LoadAsync(ViewService);
				}
			}
		}
		
		public const string ErrorPropertyName = "Error";
		private string _error;
		public string Error {
			get { return _error; }
			set {
				if (_error == value) { return; }
				RaisePropertyChanging(ErrorPropertyName);
				_error = value;
				RaisePropertyChanged(ErrorPropertyName);
			}
		}

		
		public const string MSDNUrlPropertyName = "MSDNUrl";
		private string _msdnUrl;
		public string MSDNUrl {
			get { return _msdnUrl; }
			set {
				if (_msdnUrl == value) { return; }
				RaisePropertyChanging(MSDNUrlPropertyName);
				_msdnUrl = value;
				RaisePropertyChanged(MSDNUrlPropertyName);
			}
		}

		#endregion

		bool CanLoadSelectedProperty() { return Selected.Count > 0; }
		public virtual IEnumerable<string> PreloadProperties { get { return new string[] { SchemaXml_Property }; } }
		public virtual IEnumerable<string> SkippedProperties { get { return null; } }

		#region methods
		public void Refresh(IViewService svcs) {
			lock (_sync) { DataLoaded = false; }
			LoadAsync(svcs);
		}
		protected void SetSchemaXml(PropertyInfo property) {
			try {
				string xml = Convert.ToString(property.GetValue(ClientObject));
				XmlDocument doc = new XmlDocument();
				doc.LoadXml(xml);
				SchemaXml = doc;
				HasSchemaXml = true;
			} catch (Exception err) {
				Log.Error(err);
			}
		}
		protected List<Expression<Func<ClientObject, object>>> LoadProperties() {
			List<Expression<Func<ClientObject, object>>> list = new List<Expression<Func<ClientObject, object>>>();
			foreach (string propertyName in PreloadProperties) {
				Type type = ClientObject.GetType();
				PropertyInfo property = type.GetProperty(propertyName);
				if (property != null) {
					ParameterExpression args = Expression.Parameter(typeof(ClientObject), "args");
					UnaryExpression typeArgs = Expression.Convert(args, type);
					MemberExpression propertyExpression = Expression.Property(typeArgs, property);
					UnaryExpression castExpression = Expression.Convert(propertyExpression, typeof(object));
					Expression<Func<ClientObject, object>> expression = Expression.Lambda<Func<ClientObject, object>>(castExpression, args);
					list.Add(expression);
				}
			}
			return list;
		}

		protected virtual Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>> Load() {
			Type type = ClientObject.GetType();
			List<ObjectProperty> properties = new List<ObjectProperty>();
			List<ClientObjectViewModel> children = new List<ClientObjectViewModel>();
			Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>> tuple = new Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>>(properties, children);

			if (ClientObject.ServerObjectIsNull.HasValue && ClientObject.ServerObjectIsNull.Value) {
				Error = null;
				return tuple;
			}

			if (ReloadObject) {
				Context.Load(ClientObject);
				Context.ExecuteQuery();
			}
			
			foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty)) {
				if (SkippedProperties != null && SkippedProperties.Contains(property.Name)) {
					continue;
				}
				if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) {
					if (property.Name == SchemaXml_Property) {
						SetSchemaXml(property);
					} else {
						properties.Add(new ObjectProperty().Set(ClientObject, property));
					}
				} else if (property.PropertyType.IsSubclassOf(typeof(ClientObject)) && property.GetIndexParameters().Length	== 0) {
					ClientObjectViewModel vm = ClientObjectFactory.GetViewModel(Context, property.Name, property.PropertyType, (ClientObject)property.GetValue(ClientObject));
					vm.ReloadObject = true;
					children.Add(vm);
				}
			}
			children.Sort();
			return tuple;
		}
		public virtual string GetTreeItemTitle() {
			return Name;
		}

		public virtual string GetContentTitle() {
			return Name;
		}
		public async override void LoadAsync(WPF.Common.IViewService svcs) {
			lock (_sync) {
				if (DataLoaded) {
					return;
				} else {
					DataLoaded = true;
				}
			}
			try {
				IsLoading = true;
				TreeItemTitle = string.Format("{0} (Loading)", Name);
				TypeName = ClientObject.GetType().FullName;

				MSDNUrl = string.Format(SharePointCfgSection.GetSection(false).Settings.MsdnURL, TypeName);

				Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>> result;
				Func<Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>>> func = Load;
				result = await Task.Run<Tuple<IEnumerable<ObjectProperty>, IEnumerable<ClientObjectViewModel>>>(func);

				Properties.Clear();
				foreach (var item in result.Item1) { Properties.Add(item); }
				Children.Clear();
				foreach (var item in result.Item2) { Children.Add(item); }
				if (ClientObject.ServerObjectIsNull.HasValue && ClientObject.ServerObjectIsNull.Value) {
					TreeItemTitle = string.Format("{0} (Null)", Name);
					Title = string.Format("{0} (Null)", Name);
				} else {
					TreeItemTitle = GetTreeItemTitle();
					Title = GetContentTitle();
				}
				Error = null;
			} catch (Exception err) {
				lock (_sync) { DataLoaded = false; }
				TreeItemTitle = string.Format("{0} (Error)", Name);
				StringBuilder errorMsg = new StringBuilder();
				while (err != null) {
					errorMsg.AppendLine(Convert.ToString(err));
					Log.Error(err);
					err = err.InnerException;
				}
				Error = errorMsg.ToString();
			} finally {
				IsLoading = false;
			}
		}

		public int CompareTo(ClientObjectViewModel other) {
			return this.TreeItemTitle.CompareTo(other.TreeItemTitle);
		}
		#endregion
	}
}
